"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by lane.chang on '07/09/2022'
    comment: 定义的一些装饰器(service层之上依赖)
"""
import json
import asyncio
import datetime
import functools
import time
import inspect

from fastapi.responses import JSONResponse

from project.core.exception import NotPathError, BizError
from project.lib.common import logger, logger_abnormal, session_maker
from laner.pydantic import BaseModel

from project.core.function import ResponseModel, handler_text, json_response
from project.core.log import Colors


def exec_duration(log: bool = True, add_return: bool = False, over_time=800):
    """执行时长装饰器
    :param log: 是否记录日志
    :param add_return: 是否添加到返回值
    :param over_time: 超时设定 单位： ms
    :return:
    """

    def handle_func(func):

        @functools.wraps(func)
        async def wrap_func(*args, **kwargs):
            request = kwargs.get('request')
            t1 = datetime.datetime.now().microsecond  # pylint: disable=C0103
            t2 = time.mktime(datetime.datetime.now().timetuple())  # pylint: disable=C0103
            if asyncio.iscoroutinefunction(func):
                ret = await func(*args, **kwargs)
            else:
                ret = func(*args, **kwargs)
            t3 = datetime.datetime.now().microsecond  # pylint: disable=C0103
            t4 = time.mktime(datetime.datetime.now().timetuple())  # pylint: disable=C0103
            exec_time = (t4 - t2) * 1000 + (t3 - t1) / 1000

            if log:
                log_text = f"函数【{func.__name__}】执行时长: {exec_time}ms"
                logger.info(log_text, '' if not request else request.state.request_id)
                if exec_time >= over_time:
                    logger_abnormal.warning(log_text)

            if add_return:
                if isinstance(ret, dict):
                    ret['exec_time'] = exec_time
                elif isinstance(ret, JSONResponse):
                    data = json.loads(ret.body)
                    data['exec_time'] = exec_time
                    ret = JSONResponse(data)
                elif isinstance(ret, ResponseModel):
                    ret.exec_time = exec_time

            return ret

        return wrap_func

    return handle_func


def api_common(doc_replace=None):
    """API统一封装
    :request: API请求体
    :param doc_replace:
    :return:
    """
    def handle_func(func):
        for replacer in (doc_replace or ()):
            func.__doc__ = func.__doc__.replace(replacer[0], replacer[1])

        @functools.wraps(func)
        async def wrap_func(*args, **kwargs):
            # API包含请求体
            request = kwargs.get('request')
            session = kwargs.get('session')
            response = None
            try:
                if request:
                    # body参数
                    body_params = await request.body()
                    if isinstance(body_params, bytes):
                        body_params = body_params.decode('utf-8').replace('\n', '').replace('   ', '')
                    request.state.body_params = body_params

                    # 写入body体
                    logger.info(f'body: {handler_text(body_params, 400)}', request.state.request_id)

                if 'session' in kwargs:
                    with session_maker() as session:
                        kwargs['session'] = session
                        if asyncio.iscoroutinefunction(func):
                            response = await func(*args, **kwargs)
                        else:
                            response = func(*args, **kwargs)

                else:
                    if asyncio.iscoroutinefunction(func):
                        response = await func(*args, **kwargs)
                    else:
                        response = func(*args, **kwargs)


            except NotPathError as ex:
                response = json_response(code=600, message=str(ex))
            except BizError as ex:
                response = json_response(code=ex.code, message=ex.message)
            except Exception as ex:
                raise ex
            finally:
                res_body = ''
                if isinstance(response, JSONResponse):
                    res_body = response.body.decode("utf-8").replace('\n', '').replace('   ', '')
                elif isinstance(response, ResponseModel):
                    res_body = str(response.dict()) if response else None

                logger.info(f'{handler_text(res_body, 400)}', '' if not request else request.state.request_id)
                if session:
                    session.rollback()

            return response

        return wrap_func

    return handle_func


def func_log(_logger=logger, title=''):
    """
    :param _logger:
    :param title:
    :return:
    """
    def handle_func(func):
        @functools.wraps(func)
        async def wrap_func(*args, **kwargs):
            _logger.info(f'title: {title}  function: {func.__name__} 执行日志', font_color=Colors.PURPLE.value)
            _logger.info(f'参数: ', font_color=Colors.PURPLE.value)

            params = dict(zip(inspect.signature(func).parameters.keys(), args))
            params.update(kwargs)
            for k, v in params.items():
                _logger.info(f'     @param {k}: {v}', font_color=Colors.PURPLE.value)

            try:
                t1 = datetime.datetime.now().microsecond  # pylint: disable=C0103
                t2 = time.mktime(datetime.datetime.now().timetuple())  # pylint: disable=C0103

                if asyncio.iscoroutinefunction(func):
                    ret = await func(*args, **kwargs)
                else:
                    ret = func(*args, **kwargs)

                if isinstance(ret, BaseModel):
                    ret_log = ret.dict()
                else:
                    ret_log = ret

                t3 = datetime.datetime.now().microsecond  # pylint: disable=C0103
                t4 = time.mktime(datetime.datetime.now().timetuple())  # pylint: disable=C0103
                _logger.info(f'返回: {ret_log if ret_log is not None else ""}', font_color=Colors.PURPLE.value)
                _logger.info(f'执行时长:{(t4 - t2) * 1000 + (t3 - t1) / 1000}', font_color=Colors.PURPLE.value)

                return ret
            except Exception as ex:
                _logger.critical(f'异常: {str(ex)}', exc_info=True, font_color=Colors.PURPLE.value)

        return wrap_func

    return handle_func


def retry(count=3):
    """ 方式执行失败重试次数
    :param count:
    :return:
    """
    def handle_func(func):
        @functools.wraps(func)
        async def wrap_func(*args, **kwargs):

            _count = 0
            while True:
                try:
                    if asyncio.iscoroutinefunction(func):
                        ret = await func(*args, **kwargs)
                    else:
                        ret = func(*args, **kwargs)

                    return ret

                except Exception as ex:
                    _count += 1
                    logger.info(f'执行失败，进行第{_count}次...', font_color=Colors.LIGHT_BLUE.value)
                    continue

                finally:
                    # 执行次数超过跳出
                    if _count >= count:
                        break

        return wrap_func

    return handle_func
